﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using XNOgre.Core;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;

namespace XNOgre.Graphics
{
    public class RenderWindow
    {
        private Microsoft.Xna.Framework.GameWindow gameWindow;
        private Dictionary<ViewportTarget, Camera> viewportCameraPairs;
        private List<ViewportTarget> sortedViewports;

        internal RenderWindow(Microsoft.Xna.Framework.GameWindow gameWindow)
        {
            this.gameWindow = gameWindow;
            this.gameWindow.ClientSizeChanged += this.windowSize_Changed;

            viewportCameraPairs = new Dictionary<ViewportTarget, Camera>();
            sortedViewports = new List<ViewportTarget>();
        }

        public bool IsFullScreen
        {
            get
            {
                return Root.Instance.RenderSystem.GraphicsDevice.PresentationParameters.IsFullScreen;
            }
        }
        public void ToggleFullScreen()
        {
            var presentation = Root.Instance.RenderSystem.GraphicsDevice.PresentationParameters.Clone();
            presentation.IsFullScreen = !presentation.IsFullScreen;

            var currentAdapter = Root.Instance.RenderSystem.GraphicsDevice.Adapter;
            var currentProfile = Root.Instance.RenderSystem.GraphicsDevice.GraphicsProfile;

            Root.Instance.RenderSystem.GraphicsDevice = new GraphicsDevice(currentAdapter, currentProfile, presentation);
        }

        private void windowSize_Changed(object sender, EventArgs e)
        {
            foreach (var vpt in viewportCameraPairs.Keys)
            {
                vpt.RelativeTop = gameWindow.ClientBounds.Height / vpt.ActualTop;
                vpt.RelativeLeft = gameWindow.ClientBounds.Width / vpt.ActualLeft;
                vpt.RelativeWidth = gameWindow.ClientBounds.Width / vpt.ActualWidth;
                vpt.RelativeHeight = gameWindow.ClientBounds.Height / vpt.ActualHeight;
            }
        }
        private bool HasZOrder(int zOrder)
        {
            foreach (var key in viewportCameraPairs.Keys)
            {
                if (key.ZOrder == zOrder)
                    return true;
            }
            return false;   
        }
        private void ResortViewTargets()
        {
            sortedViewports.Clear();
            
            List<ViewportTarget> viewports = new List<ViewportTarget>(viewportCameraPairs.Count);
            foreach (var key in viewportCameraPairs.Keys)
            {
                viewports.Add(key);
            }

            var sorted = from vp in viewports orderby vp.ZOrder ascending select vp;
            sortedViewports = new List<ViewportTarget>(sorted);
        }
        public void RemoveViewport(int zOrder)
        {
            for (int i = 0; i < sortedViewports.Count; i++)
            {
                if (sortedViewports[i].ZOrder == zOrder)
                {
                    viewportCameraPairs.Remove(sortedViewports[i]);
                    sortedViewports.RemoveAt(i);
                    sortedViewports[i] = null;
                }
            }

            ResortViewTargets();
        }
        public void RemoveAllViewports()
        {
            sortedViewports.Clear();
            viewportCameraPairs.Clear();
        }
        public ViewportTarget AddRenderTexture(Camera cam, int zOrder, RenderTarget2D renderTarget)
        {
            ViewportTarget vp = AddViewport(cam, zOrder);
            vp.RenderTarget = renderTarget;
            return vp;
        }
        public ViewportTarget AddViewport(Camera cam, int zOrder = 0)
        {
            return AddViewport(cam, zOrder, 0, 0, gameWindow.ClientBounds.Width, gameWindow.ClientBounds.Height);
        }
        public ViewportTarget AddViewport(Camera cam, Viewport vp, int zOrder = 0)
        {
            return AddViewport(cam, zOrder,vp.X, vp.Y, vp.Width, vp.Height);
        }
        public ViewportTarget AddViewport(Camera cam, int zOrder, int left, int top, int width, int height)
        {
            if (HasZOrder(zOrder))
            {
                throw new InvalidOperationException("A viewport with ZOrder of " + zOrder + " already exists.");
            }

            int actualX = left;
            int actualY = top;
            int actualWidth = width;
            int actualHeight = height;

            Viewport vp = new Viewport(actualX, actualY, actualWidth, actualHeight);
            cam.AspectRatio = vp.AspectRatio;

            ViewportTarget vpt = new ViewportTarget(vp, zOrder);
            vpt.ZOrderChanged += this.viewport_ZOrderChanged;
            vpt.RelativeTop = top / gameWindow.ClientBounds.Height;
            vpt.RelativeLeft = left / gameWindow.ClientBounds.Width;
            vpt.RelativeWidth = width / gameWindow.ClientBounds.Width;
            vpt.RelativeHeight = height / gameWindow.ClientBounds.Height;

            viewportCameraPairs.Add(vpt, cam);

            ResortViewTargets();

            return vpt;
        }

        void viewport_ZOrderChanged(object sender, EventArgs e)
        {
            ResortViewTargets();
        }

        internal void Render()
        {
            for (int i = 0; i < sortedViewports.Count; i++)
            {
                ViewportTarget vpt = sortedViewports[i];
                Root.Instance.RenderSystem.GraphicsDevice.Viewport = vpt.Viewport;
                Root.Instance.RenderSystem.GraphicsDevice.SetRenderTarget(vpt.RenderTarget); //may be null, indicating render to backbuffer
                Root.Instance.RenderSystem.GraphicsDevice.Clear(vpt.Color);
                viewportCameraPairs[vpt].RenderScene(vpt);
            }
        }
    }
}
